﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BuildPlannerLib.Enums;
using BuildPlannerLib.PlayerActions;
using BuildPlannerLib.SimEvents;
using BuildPlannerLib.Terran;
using BuildPlannerLib.Zerg;
using BuildPlannerLib.Protoss;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace BuildPlannerLib
{
    public class Build
    {
        // Expansion and Race enum property for expansion and race
        private Expansion expansion;
        private Race race;
        public Expansion Expansion
        {
            get { return expansion; }
            set { expansion = value; }
        }
        public Race Race
        {
            get { return race; }
            set { race = value; }
        }

        // Reference for the map
        public Map Map;

        // TimeSpan property for the current time of the simulation
        private TimeSpan currentTime;
        public TimeSpan CurrentTime
        {
            get { return currentTime; }
            set { currentTime = value; }
        }

        // Integer readonly property for the current number of workers in the simulation
        public int Workers
        {
            get
            {
                int workers = 0;
                workers += Map.GasWorkers;
                workers += Map.MineralWorkers;
                workers += Player.IdleWorkers;
                return workers;
            }
        }

        // Fields for Action and Event lists
        public ObservableCollection<PlayerAction> ActionList;
        public List<SimEvent> EventList;

        // Field for the index of the next action
        public int NextActionIndex;

        // Field for Player class
        public Player Player;

        // Constructor with parameters for mineralflags, gas layout, expansion, and race
        public Build(Race race, Expansion expansion, MineralFlags[] mineralFlags, int[] gasLayout)
        {
            Expansion = expansion;
            Race = race;

            // Race-specific class is instantiated
            switch (Race)
            {
                case Race.Terran:
                    Player = new TerranPlayer(this);
                    break;
                case Race.Zerg:
                    Player = new ZergPlayer();
                    break;
                case Race.Protoss:
                    Player = new ProtossPlayer();
                    break;
                default:
                    return;
            }

            // Creates a new map with the mineral flags and gaslayout for the first base provided
            Map = new Map(mineralFlags, gasLayout);

            // Instantiate the ActionList and EventList
            ActionList = new ObservableCollection<PlayerAction>();
            EventList = new List<SimEvent>();

            NextActionIndex = 0;

            StartMining();
        }

        // Constructor with parameters for map, expansion, and race
        public Build(Race race, Expansion expansion, Map map)
        {
            Expansion = expansion;
            Race = race;

            // Race-specific class is instantiated
            switch (Race)
            {
                case Race.Terran:
                    Player = new TerranPlayer(this);
                    break;
                case Race.Zerg:
                    Player = new ZergPlayer();
                    break;
                case Race.Protoss:
                    Player = new ProtossPlayer();
                    break;
                default:
                    return;
            }

            // Creates a new map with the mineral flags and gaslayout for the first base provided
            Map = map;

            // Instantiate the ActionList and EventList
            ActionList = new ObservableCollection<PlayerAction>();
            EventList = new List<SimEvent>();

            NextActionIndex = 0;

            StartMining();
        }

        // Method for adding the events for initial mining
        public void StartMining()
        {
            int patchCount = Map.StartingMineralFlags.Count<MineralFlags>();
            int[] patchNumber = new int[6];
            if (patchCount % 2 == 0 && patchCount > 6)
            {
                // If there is an even number of patches, the center patch is the left
                // center patch.
                patchNumber[0] = patchCount / 2 - 1;
                patchNumber[1] = patchCount / 2 - 2;
                patchNumber[2] = patchCount / 2;
                patchNumber[3] = patchCount / 2 - 3;
                patchNumber[4] = patchCount / 2 + 1;
                patchNumber[5] = patchCount / 2 + 2;
            }
            else if (patchCount > 6)
            {
                // If there is an odd number of patches, the true center patch is used.
                patchNumber[0] = (patchCount + 1) / 2;
                patchNumber[1] = (patchCount - 1) / 2;
                patchNumber[2] = (patchCount + 1) / 2 + 1;
                patchNumber[3] = (patchCount - 1) / 2 - 1;
                patchNumber[4] = (patchCount + 1) / 2 + 2;
                patchNumber[5] = (patchCount + 1) / 2 + 3;
            }
            else
            {
                // Placeholder for low mineral patch counts
            }

            // An array for the time that it takes for the first mineral mining to finish.
            // This implementation bypasses the logic that will be used later for mineral patch selection.
            int[] eventTime = new int[6];

            eventTime[0] = (Map.StartingMineralFlags[patchNumber[0]] == MineralFlags.ClosePatch) ? 480 : 532;
            eventTime[1] = (Map.StartingMineralFlags[patchNumber[1]] == MineralFlags.ClosePatch) ? 530 : 582;
            eventTime[2] = (Map.StartingMineralFlags[patchNumber[2]] == MineralFlags.ClosePatch) ? 530 : 582;
            eventTime[3] = (Map.StartingMineralFlags[patchNumber[3]] == MineralFlags.ClosePatch) ? 580 : 632;
            eventTime[4] = (Map.StartingMineralFlags[patchNumber[4]] == MineralFlags.ClosePatch) ? 580 : 632;
            eventTime[5] = (Map.StartingMineralFlags[patchNumber[5]] == MineralFlags.ClosePatch) ? 630 : 682;

            int count = 0;
            foreach (int i in patchNumber)
            {
                EventList.Add(new FinishMineralMining(this, Map.Bases.First(), Map.StartingMineralFlags[i], eventTime[count]));
                count++;
            }
        }
        
        // Method for resetting the entire build back to the beginning and clearing the action list of all time data
        public void Reset()
        {
            // Time is set to 0
            CurrentTime = TimeSpan.Zero;

            // Call Reset() method in the Map class
            Map.Reset();

            // Call Reset() method in the PlayerAction class
            foreach(PlayerAction a in ActionList)
                a.Reset();

            // Clear the event list
            EventList.Clear();

            NextActionIndex = 0;

            // Call Reset() method in the Player class
            Player.Reset();

            // Add events for initial mining
            StartMining();
        }

        // Method for resetting the entire build back to the beginning without clearing any action list time data
        public void Restart()
        {
            // Time is set to 0
            CurrentTime = TimeSpan.Zero;

            // Call Reset() method in the Map class
            Map.Reset();

            // Call Restart() method in the PlayerAction class
            foreach(PlayerAction a in ActionList)
                a.Restart();

            // Clear the event list
            EventList.Clear();

            NextActionIndex = 0;

            // Call Reset() method in the Player class
            Player.Reset();

            // Add events for initial mining
            StartMining();
        }

        // Executes the next event in the list and removes it
        public void NextEvent()
        {
            SimEvent nextEvent = EventList.First();
            nextEvent.Execute();
            CurrentTime = TimeSpan.FromMilliseconds(nextEvent.Time * 10);
            EventList.Remove(nextEvent);
        }

        // Executes the next action in the list
        public void NextAction()
        {
            ActionList.ElementAt(NextActionIndex).StartAction();
            NextActionIndex++;
        }

        // Sims until the end of the action list to refresh action data, then returns to the current time
        public void RefreshActions()
        {
            int previousTime = TimeConvert.ToInt(CurrentTime);
            Reset();

            if (ActionList.Count() > 0)
                SimToAction(ActionList.Count() - 1);

            SetTime(previousTime);
        }

        // Sims up to a specified action in the list
        public void SimToAction(int index)
        {
            // If the action list is shorter than the given index, sim to the end
            if (ActionList.Count() <= index)
                index = ActionList.Count() - 1;

            Restart();
            if (index < 0 || ActionList.Count() == 0)
            {
                return;
            }
            while (NextActionIndex <= index)
            {
                // If an action can be started, do so until no more can be started
                bool continueActions = true;
                while (ActionList.Count() != 0 && NextActionIndex < ActionList.Count() && continueActions)
                {
                    if (ActionList.ElementAt(NextActionIndex).RequirementsFilled()
                        && ActionList.ElementAt(NextActionIndex).StartCheck())
                    {
                        NextAction();
                        continueActions = true;
                    }
                    else if (ActionList.ElementAt(NextActionIndex).RequirementsFilled())
                    {
                        continueActions = false;
                    }
                    else
                    {
                        ActionList.ElementAt(NextActionIndex).Status = ActionStatus.NotPossible;
                        NextActionIndex++;
                        continueActions = true;
                    }
                    if (NextActionIndex > index)
                        return;
                }
                EventList.Sort();
                NextEvent();
            }
        }


        // Sims until a specific time
        public void SetTime(int finalTime)
        {
            // If the time is less than zero, restart the simulation and pause at 0
            if (finalTime < 0)
            {
                Restart();
                return;
            }

            // If the time is less than the current time, restart the simulation
            if (finalTime < TimeConvert.ToInt(CurrentTime))
                Restart();

            while (finalTime > TimeConvert.ToInt(CurrentTime))
            {
                // If an action can be started, do so until no more can be started
                bool continueActions = true;
                while (ActionList.Count() != 0 && NextActionIndex < ActionList.Count() && continueActions)
                {
                    if (ActionList.ElementAt(NextActionIndex).RequirementsFilled()
                        && ActionList.ElementAt(NextActionIndex).StartCheck())
                    {
                        NextAction();
                        continueActions = true;
                    }
                    else if (ActionList.ElementAt(NextActionIndex).RequirementsFilled())
                    {
                        continueActions = false;
                    }
                    else
                    {
                        ActionList.ElementAt(NextActionIndex).Status = ActionStatus.NotPossible;
                        NextActionIndex++;
                        continueActions = true;
                    }
                }
                EventList.Sort();
                if (finalTime > EventList.First().Time)
                {
                    NextEvent();
                }
                else
                {
                    CurrentTime = TimeConvert.ToTimeSpan(finalTime);
                }
            }
        }

        // Adds an action to the end of the list and sims until the start of that action
        public void AddAction(PlayerAction action, out string error)
        {
            int previousTime = TimeConvert.ToInt(CurrentTime);
            int index = NextActionIndex - 1;
            SimToAction(index);
            if (action.RequirementsFilled())
            {
                ActionList.Insert(NextActionIndex, action);
                SimToAction(NextActionIndex);
                RefreshActions();
                error = null;
            }
            else
            {
                error = action.Error;
                SetTime(previousTime);
                RefreshActions();
            }
        }

        public void RemoveAction(int actionIndex, out string error)
        {
            error = null;
            if (actionIndex < 0 || actionIndex >= ActionList.Count())
            {
                error = "Action number " + actionIndex + " does not exist.";
                return;
            }
            int previousTime = TimeConvert.ToInt(CurrentTime);
            ActionList.RemoveAt(actionIndex);
            RefreshActions();
        }

        // Sims for a given increment if time
        public void ChangeTime(int timeDifference)
        {
            int finalTime = TimeConvert.ToInt(CurrentTime) + timeDifference;
            SetTime(finalTime);
        }
    }
}
